﻿/*******************************************************************************************
 * Copyright (c) 2006-2009 Alex Robson, Rob Simmons, Craig Israel, and Rachel Twyford
 *
 * Use of this software in any form requires that you have both 
 * read and agreed to the following terms:
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions: 
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * Neither the name of Nvigorate nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************************/


using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Nvigorate.Common.Reflection;
using Nvigorate.Relational.Provider;
using Nvigorate.Extensions;
using NJ = Ninject.Core.Infrastructure;

namespace Nvigorate.Common
{

    public class TypeCaster
    {
        private static Dictionary<Type, Dictionary<Type, Func<Type, object, object>>> _specialConversions
            = new Dictionary<Type, Dictionary<Type, Func<Type, object, object>>>()
                  {
                      {
                          typeof (DBNull),
                          new Dictionary<Type, Func<Type, object, object>>()
                              {
                                  {typeof (DateTime), (t, o) => SqlDateTime.MinValue.Value},
                                  {typeof (bool), (t, o) => false},
                                  {typeof (Int16), (t, o) => (Int16)0},
                                  {typeof (Int32), (t, o) => (Int32)0},
                                  {typeof (Int64), (t, o) => (Int64)0},
                                  {typeof (Single), (t, o) => 0f},
                                  {typeof (Double), (t, o) => 0d},
                                  {typeof (Decimal), (t, o) => 0m},
                                  {typeof (Guid), (t, o) => Guid.Empty},
                                  {typeof (string), (t, o) => null},
                                  {typeof (Enum), (t, o) => Enum.ToObject(t, 0)},
                                  {typeof (Nullable<DateTime>), (t, o) => null},
                                  {typeof (Nullable<bool>), (t, o) => null},
                                  {typeof (Nullable<Int16>), (t, o) => null},
                                  {typeof (Nullable<Int32>), (t, o) => null},
                                  {typeof (Nullable<Int64>), (t, o) => null},
                                  {typeof (Nullable<Single>), (t, o) => null},
                                  {typeof (Nullable<Double>), (t, o) => null},
                                  {typeof (Nullable<Decimal>), (t, o) => null},
                                  {typeof (Nullable<Guid>), (t, o) => null},
                                  {typeof (Nullable<uint>), (t, o) => null},
                                  {typeof (Nullable<byte>), (t, o) => null},
                              }
                          },
                      {
                            typeof (string),
                            new Dictionary<Type, Func<Type, object, object>>()
                                {
                                    {typeof (Guid), (t, o) => new Guid(o.ToString())},   
                                    {typeof (DateTime), (t, o) =>
                                                                String.IsNullOrEmpty(o.ToString().Trim()) ?
                                                                    SqlDateTime.MinValue.Value :
                                                                    DateTime.Parse(o.ToString())
                                    },
                                    {typeof (Enum), (t, o) => Enum.Parse(t, o.ToString())}
                                }
                        },
                      {
                          typeof (Int16),
                          new Dictionary<Type, Func<Type, object, object>>()
                              {
                                  {typeof (Enum), (t, o) => Enum.ToObject(t, Convert.ChangeType(o, typeof (int)))}
                              }
                          },
                      {
                          typeof (Int32),
                          new Dictionary<Type, Func<Type, object, object>>()
                              {
                                  {typeof (Enum), (t, o) => Enum.ToObject(t, Convert.ChangeType(o, typeof (int)))},
                                  {typeof (Nullable<Int32>), (t, o) => (int?) o}
                              }
                          },
                      {
                          typeof (Int64),
                          new Dictionary<Type, Func<Type, object, object>>()
                              {
                                  {typeof (Enum), (t, o) => Enum.ToObject(t, Convert.ChangeType(o, typeof (int)))}
                              }
                          }, 
                      {
                         typeof(Byte),
                         new Dictionary<Type, Func<Type, object, object>>()
                             {
                                {typeof(Enum), (t, o) => Enum.ToObject(t, o)}
                             }
                        }
                  };

        private static Dictionary<Type, Type> _concreteTypes = new Dictionary<Type, Type>()
                {
                   {typeof(IList<>), typeof(StatefulList<>)},                                                    
                   {typeof(ICollection<>), typeof(StatefulCollection<>)}                                                    
                };

        private static Dictionary<Type, NJ.FactoryMethod> _factories = new Dictionary<Type, NJ.FactoryMethod>();

        public static MethodInfo GetConvertMethodInfo(Type expectedType)
        {
            return typeof(TypeCaster).GetMethod("ConvertValue", BindingFlags.Public | BindingFlags.Static, null, new [] {expectedType}, null).MakeGenericMethod(expectedType);
        }

        public static IEnumerable<T> ConvertEnumerable<T>(IEnumerable value)
        {
            return value.Cast<T>();
        }

        public static object ConvertValue(object value, Type expectedType)
        {
            return MrCastastic.CastIt(expectedType, value);
        }

        public static T ConvertValue<T>(object value)
        {
            if ((object)value == null)
            {
                return default(T);
            }

            Type expectedType = typeof (T);
            Type valueType = value.GetType();

            if(valueType.Equals(expectedType) || Reflector.HasBaseTypeOf(valueType, expectedType))
            {
                return (T) value;
            }

            IConvertible convertible = value as IConvertible;
            T newValue;

            Dictionary<Type, Func<Type, object, object>> conversions = null;
            _specialConversions.TryGetValue(valueType, out conversions);
            
            if (conversions != null)
            {
                Func<Type, object, object> converter = null;
                conversions.TryGetValue(expectedType, out converter);
                try
                {
	                if(converter != null)
	                {
	                    return (T) converter(expectedType, value);
	                }
                    //This is to support type Object which has a null BaseType
                    var baseType = expectedType.BaseType;
                    if (baseType != null)
                    {
                        conversions.TryGetValue(baseType, out converter);
                        if (converter != null)
                        {
                            return (T) converter(expectedType, value);
                        }
                    }
                }
                catch (InvalidCastException e)
                {
                    throw new Exception(
                        string.Format("An invalid cast attempt occurred in Type Caster trying to cast ({0}) {1} to {2}.",
                            valueType.FullName, value.ToString(), expectedType.FullName));
                }
            }
            
            if(convertible == null)
            {
                if (Reflector.IsEnumerable(valueType) && Reflector.IsEnumerable(expectedType))
                {
                    var expectedElementType = expectedType.GetGenericArguments().First();
                    var enumerable = MrCastastic.CastEnumerable(expectedElementType, value as IEnumerable);
                    var actualType = EnsureConcreteEnumerable(expectedType);

                    NJ.FactoryMethod factory = null;
                    _factories.TryGetValue(actualType, out factory);
                    if(factory == null)
                    {
                        factory = NJ.DynamicMethodFactory.CreateFactoryMethod(Reflector.GetConstructor(new object[] {enumerable},
                                                                                             actualType));
                        _factories[actualType] = factory;
                    }
                    newValue = (T) factory(enumerable);
                }
                else
                    newValue = NewInstance<T>(value);
            }
            else if (Reflector.IsEnumerable(valueType) && Reflector.IsEnumerable(expectedType))
            {
                var originatingCollection = value as IEnumerable;
                var expectedElementType = expectedType.GetGenericArguments().First();
                newValue = NewInstance<T>(MrCastastic.CastEnumerable(EnsureConcreteEnumerable(expectedElementType), originatingCollection));
            }
            else
            {
                try
                {
                    newValue = (T)Convert.ChangeType(value, expectedType);
                }
                catch(InvalidCastException castException)
                {
                    throw;
                }
            }

            return newValue;
        }

        internal static Type EnsureConcreteEnumerable(Type expectedType)
        {
            if(expectedType.IsInterface && expectedType.IsGenericType)
            {
                var arg = expectedType.GetGenericArguments().First();
                return _concreteTypes[expectedType.GetGenericTypeDefinition()].MakeGenericType(arg);
            }
            return expectedType;
        }

        internal static T NewInstance<T>(object parameter)
        {
            var constructor = GetConstructor(typeof(T), parameter.GetType());
            if (constructor != null)
                return (T) constructor.Invoke(new [] {parameter});

            return default(T);
        }

        internal static object NewInstance(object parameter, Type expectedType)
        {
            var constructor = GetConstructor(expectedType, parameter.GetType());

            if (constructor != null)
                return constructor.Invoke(new[] { parameter });

            return null;
        }

        internal static ConstructorInfo GetConstructor(Type expectedType, Type argument)
        {
            var equivalency = new TypeEquivalenceComparer();
            return expectedType.GetConstructors()
                .Where(c => c.GetParameters().Length == 1)
                .Where(c => equivalency.Equals(
                                c.GetParameters().First().ParameterType,
                                argument))
                .FirstOrDefault();
        }

        static TypeCaster()
        {
            
        }
    }
}
